/*
 *  $Id$
 *  Copyright [2010] [Panxiaobo] 
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */
package pxb.openid.op.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pxb.openid.Association;
import pxb.openid.Constants;
import pxb.openid.Forward;
import pxb.openid.OpenIdException;
import pxb.openid.ServletUtil;
import pxb.openid.op.AESAssociationManager;
import pxb.openid.op.AssociateAccepter;
import pxb.openid.op.AssociationManager;
import pxb.openid.op.AuthenticationResponse;
import pxb.openid.servlet.RedirectServlet;
import pxb.openid.util.Util;

/**
 * 
 * @author Panxiaobo
 * 
 */
@SuppressWarnings("serial")
public abstract class BaseOpenidProviderServlet extends RedirectServlet implements Constants {

    private static final Logger log = LoggerFactory.getLogger(BaseOpenidProviderServlet.class);

    protected AssociateAccepter associateAccepter = new AssociateAccepter();
    protected AssociationManager associationManager;
    protected AuthenticationResponse authenticationResponse = new AuthenticationResponse();

    protected String serverURL;

    public BaseOpenidProviderServlet() {
        super();
        setAssociationManager(new AESAssociationManager());
    }

    /**
     * 需要实现的方法
     * 
     * @param serverURL
     * @param association
     * @param parameters
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void acceptAuth(String serverURL, Association association, Map<String, String> parameters,
            HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        successResponse(serverURL + "?u=test", null, parameters, serverURL, association, req, resp);
    }

    protected String createXRDS(String uri) throws IOException {
        return "<Service xmlns=\"xri://$xrd*($v*2.0)\">" + "<Type>http://specs.openid.net/auth/2.0/server</Type>"
                + "<URI>" + uri + "</URI>" + "</Service>";
    }

    protected String getServerURL(HttpServletRequest req) {
        if (serverURL == null)
            return ServletUtil.getRequestUrl(req);
        return serverURL;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        String serverURL = config.getInitParameter("server.url");
        if (serverURL == null) {
            serverURL = System.getProperty("openid.op.server.url");
        }
        this.serverURL = serverURL;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest,
     * javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String mode = req.getParameter(OPENID_MODE);

        if (mode == null || "".equals(mode)) {
            String s = createXRDS(getServerURL(req));
            resp.setContentType("application/xrds+xml");
            PrintWriter out = resp.getWriter();
            out.print(s);
            out.flush();
            out.close();
            return;
        } else {
            Map<String, String> parameters = ServletUtil.getRequestArgs(req, "openid.");
            if ("associate".equals(mode)) {
                Map<String, String> rspMap = associateAccepter.accept(req.isSecure(), parameters, associationManager);
                // Out
                resp.setContentType("text/plain");
                PrintWriter out = resp.getWriter();
                for (Map.Entry<String, String> entry : rspMap.entrySet()) {
                    out.println(entry.getKey() + ":" + entry.getValue());
                }
                out.flush();
                out.close();
                return;
            } else if ("checkid_setup".equals(mode)) {

                if (log.isDebugEnabled()) {
                    log.debug("OP <=- RP auth request:");
                    Map<String, String> map = new TreeMap<String, String>(parameters);
                    for (Map.Entry<String, String> entry : map.entrySet()) {
                        log.debug("{} = {}", entry.getKey(), entry.getValue());
                    }
                    log.debug("==================== End");
                }

                try {
                    String handle = parameters.get(OPENID_ASSOCIATION_HANDLE);
                    Util.checkNotNull("handle should't null.", handle);
                    Association association = associationManager.get(handle);
                    Util.checkNotNull("association not found.", association);
                    if (association.getExpireTime().before(new Date())) {
                        throw new OpenIdException("Association expired");
                    }
                    acceptAuth(getServerURL(req), association, parameters, req, resp);
                    return;
                } catch (Exception e) {
                    log.error("error in checkid_setup", e);
                    redirect(req, resp,
                            authenticationResponse.generateErrorAuth(parameters.get(OPENID_RETURN_TO), e.getMessage()));
                    return;
                }
            }
            redirect(req, resp, authenticationResponse.generateCancelAuth(parameters.get(OPENID_RETURN_TO)));
        }

    }

    /**
     * @param associationManager
     *            the associationManager to set
     */
    protected void setAssociationManager(AssociationManager associationManager) {
        this.associationManager = associationManager;
    }

    /**
     * 返回正确的答复
     * 
     * @param userURL
     * @param values
     * @param parameters
     * @param serverURL
     * @param association
     * @param req
     * @param resp
     * @throws IOException
     * @throws ServletException
     */
    protected void successResponse(String userURL, Map<String, String> values, Map<String, String> parameters,
            String serverURL, Association association, HttpServletRequest req, HttpServletResponse resp)
            throws IOException, ServletException {
        Map<String, String> respValues = new HashMap<String, String>();
        if (values != null)
            respValues.putAll(values);
        if (userURL.equals(parameters.get("openid.claimed_id"))) {
            respValues.put("openid.claimed_id", parameters.get("openid.claimed_id"));
            respValues.put("openid.identity", Constants.ID_SELECT);
        } else {
            respValues.put("openid.claimed_id", userURL);
            respValues.put("openid.identity", userURL);
        }
        Forward toRP = authenticationResponse.generateSuccessAuth(serverURL, association,
                parameters.get(Constants.OPENID_RETURN_TO), respValues);
        redirect(req, resp, toRP);
    }

}
